<?php
/**
 * 多进程处理类.
 */
namespace Lib\Core;

declare(ticks=1);

class JobDaemon extends \Home {

    public $maxProcesses = 10;
    protected $currentJobs = array();
    protected $signalQueue = array();
    protected $finishedJobsNumber = 0;
    protected $manProcessPId;
    protected $childProcess;
    protected $mainProcessFunc;
    protected $childParams;
    protected $mainParams;
    protected $logDir;
    protected $log;

    public function __construct(){
        $this->manProcessPId = getmypid();
        $this->logDir = 'processes/' . $this->manProcessPId;
        $this->log = new \Lib\Core\Log();
        pcntl_signal(SIGCHLD, array($this, 'childSignalHandler'));
        $this->log('man process start:' . $this->manProcessPId);
    }

    public function getManProcessPId() {
        return $this->manProcessPId;
    }

    /**
     * 注册一个子进程执行的逻辑.
     *
     * @param mixed   $func            注册一个子进程的逻辑.
     * @param integer $processesNumber 最大子进程数.
     *
     * @param void
     */
    public function registerChildProcess($func, $processesNumber = 10, $params = null) {
        $this->childProcess = $func;
        $this->childParams = $params;
        $this->maxProcesses = $processesNumber;
    }

    /**
     * 注册一个子进程执行的逻辑.
     *
     * @param mixed   $func            注册一个子进程的逻辑.
     *
     * @param void
     */
    public function registerManProcess($func, $params = null) {
        $this->mainProcessFunc = $func;
        $this->mainParams = $params;
    }

    public function run(){
        if (!$this->childProcess) {
            echo '请使用childProcess方法设置只进程处理逻辑.' . PHP_EOL;exit;
        }
        while (true) {
            if ($this->finishedJobsNumber >= $this->maxProcesses) {
                break;
            }
            if (count($this->currentJobs) < $this->maxProcesses - $this->finishedJobsNumber) {
                if ($this->finishedJobsNumber >= $this->maxProcesses) {
                    break;
                }
                $this->launchJob();
            } else {
                usleep(500);
            }
        }
        //Wait for child processes to finish before exiting here
        while(count($this->currentJobs)){
            // 等待子进程执行完毕.
            sleep(1);
        }
        $this->log('man process finished');
    }

    /**
     * Launch a job from the job queue
     */
    protected function launchJob(){
        $pid = pcntl_fork();
        if($pid == -1){
            //Problem launching the job
            return false;
        }
        else if ($pid){
            if ($this->mainProcessFunc) {
                call_user_func($this->mainProcessFunc, $this->mainParams);
                $this->mainProcessFunc = null;
                $this->mainParams = null;
            }
            // Parent process
            // Sometimes you can receive a signal to the childSignalHandler function before this code executes if
            // the child script executes quickly enough!
            $this->currentJobs[$pid] = $pid;
            // In the event that a signal for this pid was caught before we get here, it will be in our signalQueue array
            // So let's go ahead and process it now as if we'd just received the signal
            if(isset($this->signalQueue[$pid])){
                // 这个子进程已经存在就关闭
                $this->childSignalHandler(SIGCHLD);
                unset($this->signalQueue[$pid]);
            }
        }
        else {
            // 子进程处理逻辑.
            $this->log('child process start:' . getmypid());
            call_user_func($this->childProcess, $this->childParams);
            exit;
        }
        return true;
    }

    public function childSignalHandler($sig){
        switch ($sig) {
            case SIGCHLD:
                //If no pid is provided, that means we're getting the signal from the system.  Let's figure out
                //which child process ended
                $pid = pcntl_waitpid(-1, $status, WNOHANG);
                //Make sure we get all of the exited children
                while($pid > 0){
                    if($pid && isset($this->currentJobs[$pid])){
                        $jobId = $this->currentJobs[$pid];
                        $exitCode = pcntl_wexitstatus($status);
                        if($exitCode != 0){
                            // 子进程异常退出
                            $this->log($pid . ' exited with status ' . $exitCode);
                        } else {
                            // 标记进程结束.
                            $this->log($pid . ' finished');
                            $this->finishedJobsNumber++;
                        }
                        // 删除当前允许的进程.
                        unset($this->currentJobs[$pid]);
                    }
                    else if($pid){
                        //Oh no, our job has finished before this parent process could even note that it had been launched!
                        //Let's make note of it and handle it when the parent process is ready for it
                        $this->signalQueue[$pid] = $status;
                    }
                    $pid = pcntl_waitpid(-1, $status, WNOHANG);
                }
                return true;
                break;
        }
    }

    protected function log($msg) {
        $this->log->write($this->logDir, $msg);
    }
}
